package drds.plus.parser.parser.dml;

import drds.plus.parser.abstract_syntax_tree.expression.Expression;
import drds.plus.parser.abstract_syntax_tree.expression.primary.misc.Identifier;
import drds.plus.parser.abstract_syntax_tree.statement.Query;
import drds.plus.parser.abstract_syntax_tree.statement.QueryStatement;
import drds.plus.parser.abstract_syntax_tree.statement.SelectStatement;
import drds.plus.parser.abstract_syntax_tree.statement.UnionStatement;
import drds.plus.parser.abstract_syntax_tree.statement.select.GroupBy;
import drds.plus.parser.abstract_syntax_tree.statement.select.Order;
import drds.plus.parser.abstract_syntax_tree.statement.select.OrderBy;
import drds.plus.parser.abstract_syntax_tree.statement.select.table.*;
import drds.plus.parser.lexer.Lexer;
import drds.plus.parser.lexer.Token;
import drds.plus.parser.parser.ExpressionParser;
import drds.plus.parser.parser.Parser;
import drds.plus.parser.parser.SelectParser;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public abstract class DmlParser extends Parser {

    protected ExpressionParser expressionParser;

    public DmlParser(Lexer lexer, ExpressionParser expressionParser) {
        super(lexer);
        this.expressionParser = expressionParser;
    }


    protected GroupBy groupBy() {
        if (lexer.token() != Token.KW_GROUP) {
            return null;
        }
        lexer.nextToken();
        match(Token.KW_BY);
        lexer.nextToken();
        Expression expression = expressionParser.expression();
        GroupBy groupBy = new GroupBy();
        Order order = null;
        //
        switch (lexer.token()) {
            case KW_ASC:
                order = Order.asc;
                lexer.nextToken();
                break;
            case KW_DESC:
                order = Order.desc;
                lexer.nextToken();
                break;
            default:
                //throw new IllegalArgumentException();
                order = Order.asc;
                break;//已经略过asc desc
        }
        groupBy.addGroupByItem(expression, order);
        switch (lexer.token()) {
            case PUNC_COMMA:
                break;
            default:
                return new GroupBy(expression, order);
        }
        while (lexer.token() == Token.PUNC_COMMA) {
            lexer.nextToken();
            expression = expressionParser.expression();
            switch (lexer.token()) {
                case KW_DESC:
                    order = Order.desc;
                    lexer.nextToken();
                    break;
                case KW_ASC:
                    order = Order.asc;
                    lexer.nextToken();
                    break;
                default:
                    //throw new IllegalArgumentException();
                    order = Order.asc;
                    break;//已经略过asc desc
            }
            groupBy.addGroupByItem(expression, order);
        }
        return groupBy;
    }

    protected OrderBy orderBy() {
        if (lexer.token() != Token.KW_ORDER) {
            return null;
        }
        lexer.nextToken();
        match(Token.KW_BY);
        lexer.nextToken();
        Expression expression = expressionParser.expression();
        OrderBy orderBy = new OrderBy();
        Order order = null;
        //
        switch (lexer.token()) {
            case KW_ASC:
                order = Order.asc;
                break;
            case KW_DESC:
                order = Order.desc;
                break;
            default:
                throw new IllegalArgumentException();
        }
        orderBy.addOrderByItem(expression, order);
        lexer.nextToken();
        switch (lexer.token()) {
            case PUNC_COMMA:
                break;
            default:
                return new OrderBy(expression, order);
        }
        while (lexer.token() == Token.PUNC_COMMA) {
            lexer.nextToken();
            expression = expressionParser.expression();
            switch (lexer.token()) {
                case KW_DESC:
                    order = Order.desc;
                    break;
                case KW_ASC:
                    order = Order.asc;
                    break;
                default:
                    throw new IllegalArgumentException();
            }
            orderBy.addOrderByItem(expression, order);
            lexer.nextToken();
        }
        return orderBy;
    }


    //////////////////////////////////////////////////////////////////////////////

    /**
     * @return never null
     */
    public Tables tables() {
        ITable table = table();
        return buildTables(table);
    }

    public ITable table() {
        ITable table = buildTable();
        return buildTable(table);
    }

    private ITable buildTable() {
        String alias = null;
        switch (lexer.token()) {
            case PUNC_LEFT_PAREN://(需要带别名
                lexer.nextToken();
                Object object = tryQuery();
                match(Token.PUNC_RIGHT_PAREN);
                lexer.nextToken();
                if (object instanceof Query) {
                    alias = alias();
                    return new SubQuery((Query) object, alias);
                }
                return (ITable) object;
            case IDENTIFIER:
                Identifier tableName = identifier();
                alias = alias();
                return new Table(tableName, alias);
            default:
                throw error("unexpected token for buildTable: " + lexer.token());
        }
    }

    protected String alias() {
        if (lexer.token() == Token.KW_AS) {
            lexer.nextToken();
            match(Token.IDENTIFIER);
            String alias = lexer.stringValue();
            lexer.nextToken();
            return alias;
        }
        return null;
    }

    private Tables buildTables(ITable table) {
        List<ITable> tableList;
        if (lexer.token() == Token.PUNC_COMMA) {
            tableList = new ArrayList<ITable>();
            tableList.add(table);
            while (lexer.token() == Token.PUNC_COMMA) {
                lexer.nextToken();
                table = table();
                tableList.add(table);
            }
        } else {
            tableList = new ArrayList<ITable>(1);
            tableList.add(table);
        }
        return new Tables(tableList);
    }

    private ITable buildTable(ITable left) {
        while (true) {
            ITable right;
            Expression on;
            boolean isLeft = true;
            switch (lexer.token()) {
                case KW_JOIN: {
                    lexer.nextToken();
                    right = buildTable();
                    switch (lexer.token()) {
                        case KW_ON:
                            lexer.nextToken();
                            on = expressionParser.expression();
                            left = new InnerJoin(left, right, on);
                            break;
                        default:
                            left = new InnerJoin(left, right);
                            break;
                    }
                    break;
                }
                ////////////////////////////////////////////////////////////
                case KW_LEFT:
                    isLeft = true;
                case KW_RIGHT:
                    isLeft = false;
                {
                    lexer.nextToken();
                    match(Token.KW_JOIN);
                    right = table();
                    switch (lexer.token()) {
                        case KW_ON:
                            lexer.nextToken();
                            on = expressionParser.expression();
                            left = new OuterJoin(isLeft, left, right, on);
                            break;

                        default:
                            throw error("conditionExpr cannot be null for outer join");
                    }
                    break;
                }
                ////////////////////////////////////////////////////////////
                default:
                    return left;
            }
        }
    }


    /**
     * @return type of {@link Query} or {@link Tables}
     */
    private Object tryQuery() {
        Object object;
        switch (lexer.token()) {
            case KW_SELECT:
                SelectStatement selectStatement = trySelectStatement();
                return union(selectStatement);
            case PUNC_LEFT_PAREN:
                lexer.nextToken();
                object = tryQuery();
                match(Token.PUNC_RIGHT_PAREN);
                if (object instanceof Query) {
                    if (object instanceof SelectStatement) {
                        Query query = union((SelectStatement) object);
                        if (query != object) {
                            return query;
                        }
                    }
                    String alias = alias();
                    if (alias != null) {
                        object = new SubQuery((Query) object, alias);
                    } else {
                        return object;
                    }
                }
                object = buildTable((ITable) object);
                break;
            default:
                object = table();
        }

        List<ITable> tableList;
        if (lexer.token() != Token.PUNC_COMMA) {
            tableList = new ArrayList<ITable>(1);
            tableList.add((ITable) object);
            return new Tables(tableList);
        } else {
            tableList = new LinkedList<ITable>();
            tableList.add((ITable) object);
            while (lexer.token() == Token.PUNC_COMMA) {
                lexer.nextToken();
                object = table();
                tableList.add((ITable) object);
            }
            return new Tables(tableList);
        }
    }


    /**
     * @return argument itself if there is no union
     */
    protected QueryStatement union(SelectStatement selectStatement) {
        if (lexer.token() != Token.KW_UNION) {
            return selectStatement;
        }
        UnionStatement unionStatement = new UnionStatement(selectStatement);
        for (; lexer.token() == Token.KW_UNION; ) {
            lexer.nextToken();
            boolean isAll = false;
            switch (lexer.token()) {
                case KW_ALL:
                    isAll = true;
                case KW_DISTINCT:
                    lexer.nextToken();
                    break;
            }
            selectStatement = trySelectStatement();
            unionStatement.addSelectStatement(selectStatement, isAll);
            lexer.nextToken();//快速略过)
        }
        unionStatement.setOrderBy(orderBy());
        unionStatement.setLimit(limit());
        return unionStatement;
    }

    protected SelectStatement trySelectStatement() {
        switch (lexer.token()) {
            case KW_SELECT:
                return selectStatement();
            case PUNC_LEFT_PAREN:
                lexer.nextToken();
                SelectStatement selectStatement = trySelectStatement();
                match(Token.PUNC_RIGHT_PAREN);
                return selectStatement;
            default:
                throw error("unexpected token: " + lexer.token());
        }
    }

    public SelectStatement selectStatement() {
        return new SelectParser(lexer, expressionParser).selectStatement();
    }

}
